package com.ruijie.camerax.media;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * author: luqihua
 * date:2018/8/16
 * description:
 **/
public class VideoEncoder extends MediaCodec.Callback {
    private static final String TAG = "VideoEncoder";
    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
    private MediaCodec mMediaCodec;

    private MediaFormat mMediaFormat;

    private Surface mInputSurface;
    private boolean hasInit = false;
    private boolean isStart = false;

    // 填充好的队列
    private final ArrayBlockingQueue<YUV420Frame> fillYUV420FramesQueue = new ArrayBlockingQueue<>(20);
    // 未填充数据的队列
    private final ArrayBlockingQueue<YUV420Frame> emptyYUV420FramesQueue = new ArrayBlockingQueue<>(20);

    // 编码好的h264数据队列
    private final ArrayBlockingQueue<VideoFrame> h264FrameQueue = new ArrayBlockingQueue<>(20);
    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;

    private final byte[] logBuffer = new byte[20];
    private int len = 0;

    public void initFrameQueue(int width, int height) {

        fillYUV420FramesQueue.clear();
        emptyYUV420FramesQueue.clear();
        //初始化空对象
        int size = emptyYUV420FramesQueue.remainingCapacity();
        for (int i = 0; i < size; i++) {
            emptyYUV420FramesQueue.offer(new YUV420Frame(width, height));
        }
    }

    /**
     * @param width
     * @param height
     * @param orientation
     * @param useInputSurface 是否创建surface用于接收输入
     * @throws IOException
     */
    public void init(int width, int height, int orientation, boolean useInputSurface) throws IOException {
        Log.d(TAG, "init: " + width + "/" + height + ":" + orientation);
        initFrameQueue(width, height);
        mMediaFormat = MediaFormatCreate.createFormat(MIME_TYPE, width, height, orientation, useInputSurface);
        //配置编码参数
        mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            startBackgroundThread();
            mMediaCodec.setCallback(this, mBackgroundHandler);
        } else {
            mMediaCodec.setCallback(this);
        }

        mMediaCodec.configure(mMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        if (useInputSurface) {
            mInputSurface = mMediaCodec.createInputSurface();
        }

        MediaFormatCreate.showSupportColorFormat(mMediaCodec.getCodecInfo());

        ByteBuffer spsBuffer = mMediaCodec.getOutputFormat().getByteBuffer("csd-0");
        ByteBuffer ppsBuffer = mMediaCodec.getOutputFormat().getByteBuffer("csd-1");

        if (spsBuffer != null) {
            Log.d(TAG, "sps:  " + Arrays.toString(spsBuffer.array()));
        }
        if (ppsBuffer != null) {
            Log.d(TAG, "pps:  " + Arrays.toString(ppsBuffer.array()));
        }

        hasInit = true;
    }


    @Override
    public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
        StreamLog.d(TAG, "encoder input: " + fillYUV420FramesQueue.size());
        if (!isStart || mInputSurface != null) return;
        ByteBuffer inputBuffer = codec.getInputBuffer(index);
        if (inputBuffer == null) return;
        inputBuffer.clear();
        //从已填充数据队列取帧数据
        YUV420Frame yuv420Frame = fillYUV420FramesQueue.poll();
        if (yuv420Frame != null) {
            inputBuffer.put(yuv420Frame.yuvBytes);
            //时间戳必须是System.nanoTime()
            codec.queueInputBuffer(index, 0, inputBuffer.position(), yuv420Frame.timestamp, 0);
            //归还缓存到队列
            emptyYUV420FramesQueue.offer(yuv420Frame);
        } else {
            codec.queueInputBuffer(index, 0, inputBuffer.position(), 0, 0);
        }
    }


    @Override
    public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        if (!isStart) return;
        ByteBuffer outputBuffer = codec.getOutputBuffer(index);
        if (outputBuffer != null && info.size > 0) {
            StreamLog.d(TAG, "offset = " + info.offset + ";size = " + info.size + ";remaining = " + outputBuffer.remaining());

            outputBuffer.position(info.offset);
            byte[] buffer = new byte[info.size];
            outputBuffer.get(buffer);

            if (len++ < 10) {
                System.arraycopy(buffer, 0, logBuffer, 0, Math.min(buffer.length, 20));
                StreamLog.d(TAG, "NALU = " + (info.size) + ":" + Arrays.toString(logBuffer));
            }

            VideoFrame videoFrame = new VideoFrame(buffer, info);
            if (h264FrameQueue.offer(videoFrame)) {
                StreamLog.d(TAG, "h264  Offer success");
            } else {
                StreamLog.d(TAG, "h264  Offer to queue failed, queue in full state");
            }
        }
        codec.releaseOutputBuffer(index, false);
    }

    @Override
    public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
        Log.e(TAG, "onError: ", e);
//        codec.reset();
//        configure();
//        codec.start();
    }

    @Override
    public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
        mMediaFormat = format;
    }


    public VideoFrame pollH264Data() {
        return h264FrameQueue.poll();
    }


    public int available() {
        return h264FrameQueue.size();
    }


    public void start() {
        if (!hasInit || isStart) return;
        mMediaCodec.start();
        Log.d(TAG, "start success");
        isStart = true;
    }

    public Surface getSurface() {
        return mInputSurface;
    }


    @Nullable
    public YUV420Frame pollYUVBuffer() {
        if (!isStart) return null;
        return emptyYUV420FramesQueue.poll();
    }

    /**
     * 原始数据进行编码
     *
     * @param yuv420Frame
     */
    public void offerYUVBuffer(YUV420Frame yuv420Frame) {
        if (!isStart) return;
        if (fillYUV420FramesQueue.offer(yuv420Frame)) {
            StreamLog.d(TAG, "yuv Offer to queue success");
        } else {
            StreamLog.d(TAG, "yuv Offer to queue failed, queue in full state");
        }
    }


    public void stop() {
        isStart = false;
    }

    public void release() {
        try {
            if (mMediaCodec != null) {
                isStart = false;
                mMediaCodec.stop();
                mMediaCodec.release();
                mMediaCodec = null;
            }
            //最后关闭线程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
